package com.chenfan.mcn.extension;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.chenfan.privilege.common.config.UrlAuth;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * MCN代码规范约束扫描器
 *
 * @author wulg
 * @date 2021-10-29
 **/
@SuppressWarnings("AlibabaRemoveCommentedCode")
@Slf4j
@Profile(value={"local","dev"})
public class CodeStyleScanner implements ApplicationListener<ApplicationReadyEvent> {

    /**
     * 防止二次刷新
     */
    private static AtomicBoolean initialed = new AtomicBoolean(false);

    /**
     * 包名前缀
     */
    private String packageNamePrefix;

    /**
     * Mapper Class
     */
    private Class<? extends BaseMapper> enhancedMapperClass;

    /**
     * Response Class
     */
    private Class<?> responseClass;


    public CodeStyleScanner(String packageNamePrefix, Class<?> responseClass, Class<? extends BaseMapper> enhancedMapperClass) {
        this.packageNamePrefix = packageNamePrefix;
        this.enhancedMapperClass = enhancedMapperClass;
        this.responseClass = responseClass;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        if (initialed.compareAndSet(false,true)) {
            //检查Entity类
            this.checkEntity();
            //校验所有Mapper
            this.checkMappers(applicationContext);
            //校验所有Service
            this.checkServices(applicationContext);
            //校验所有Controller
            this.checkControllers(applicationContext);
        }
    }

    /**
     * 检查Entity类是否干净
     */
    private void checkEntity() {
        TableInfoHelper.getTableInfos().forEach(tableInfo -> {
            List<Field> fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(tableInfo.getEntityType()));
            List<Field> noExistField = fieldList.stream().filter(field -> {
                TableField tableField = field.getAnnotation(TableField.class);
                return (tableField != null && !tableField.exist());
            }).collect(toList());
            if(CollectionUtils.isNotEmpty(noExistField)) {
                log.error("【代码不规范提醒-PO对象】{}不允许添加额外属性 @TableField(exist = false)", tableInfo.getEntityType().getSimpleName());
            }
            Class<?> entityClass = tableInfo.getEntityType();
            //PO对象必须加@TabalName
            if(!entityClass.isAnnotationPresent(TableName.class)){
                log.error("【代码不规范提醒-PO对象】{}类上必须标记@TableName", entityClass.getSimpleName());
            }
        });
    }

    /**
     * 检查Mapper
     * @param applicationContext
     */
    private void checkMappers(ApplicationContext applicationContext) {
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
        Collection<Class<?>> mapperList = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers();
        if(CollectionUtils.isEmpty(mapperList)) {
            return;
        }
        for(Class<?> mapperClass : mapperList) {
            String packageName = mapperClass.getPackage().getName();
            if(!packageName.startsWith(packageNamePrefix)) {
                continue;
            }
            if(!enhancedMapperClass.isAssignableFrom(mapperClass)) {
                log.error("【代码不规范提醒-Mapper对象】{}推荐继承{}", mapperClass.getSimpleName(), enhancedMapperClass.getSimpleName());
            }
            if(!BaseMapper.class.isAssignableFrom(mapperClass)) {
                log.error("【代码不规范提醒-Mapper对象】{}未继承{}/{}", mapperClass.getSimpleName(), BaseMapper.class.getSimpleName(), enhancedMapperClass.getSimpleName());
            }
        }
    }

    /**
     * 检查Controller
     * @param applicationContext
     */
    private void checkControllers(ApplicationContext applicationContext) {
        Map<String, Object> controllers = applicationContext.getBeansWithAnnotation(Controller.class);
        if(MapUtils.isEmpty(controllers)) {
            return;
        }
        Map<String, AtomicInteger> urlCodeMap = new HashMap<>();
        for(Map.Entry<String, Object> entry : controllers.entrySet()) {
            Class<?> controllerClass = entry.getValue().getClass();
            String packageName = controllerClass.getPackage().getName();
            if(!packageName.startsWith(packageNamePrefix)) {
                continue;
            }
            if(!controllerClass.isAnnotationPresent(RestController.class)){
                log.error("【代码不规范提醒】{}未标记@RestController", controllerClass.getSimpleName());
            }
            Method[] methods = controllerClass.getMethods();
            for (Method method : methods) {
                //检查API
                if(method.isAnnotationPresent(RequestMapping.class)
                        || method.isAnnotationPresent(PostMapping.class)
                        || method.isAnnotationPresent(GetMapping.class)
                        || method.isAnnotationPresent(PutMapping.class)
                        || method.isAnnotationPresent(DeleteMapping.class)){
                    this.checkApi(controllerClass, method, urlCodeMap);
                }
            }
        }
        urlCodeMap.entrySet().forEach(e -> {
            if(e.getValue().get() > 1) {
                log.error("【代码不规范提醒-@UrlAuth】{}配了{}个不同接口", e.getKey(), e.getValue());
            }
        });
    }

    /**
     * 检查service
     * @param applicationContext
     */
    private void checkServices(ApplicationContext applicationContext) {
        Map<String, Object> services = applicationContext.getBeansWithAnnotation(Service.class);
        if(MapUtils.isEmpty(services)) {
            return;
        }
        for(Map.Entry<String, Object> entry : services.entrySet()) {
            Class<?> serviceClass = entry.getValue().getClass();
            Package packageObj = serviceClass.getPackage();
            if(Objects.isNull(packageObj) || !packageObj.getName().startsWith(packageNamePrefix)) {
                continue;
            }
            Method[] methods = serviceClass.getMethods();
            for (Method method : methods) {
                //检查@Service
                if(responseClass.isAssignableFrom(method.getReturnType())){
                    log.error("【代码不规范提醒】{}#{} --> 不允许返回Response对象（包含继承扩展的）！{}",
                            serviceClass.getSimpleName(), method.getName(), responseClass.getName());
                }
            }
        }
    }

    /**
     * 检查接口
     * @param method
     */
    private void checkApi(Class<?> controllerClass, Method method, Map<String, AtomicInteger> urlCodeMap) {
        String controllerName = controllerClass.getSimpleName();
        String methodName = method.getName();
        //校查API响应规范
        Class<?> returnClass = method.getReturnType();
        //检查API接口相应必须用Response封装，不然无响应码信息
//        if(!returnClass.isAssignableFrom(responseClass)) {
//            log.error("【代码不规范提醒】{}#{} --> 接口响应必须用Response（含继承）封装 {}", controllerName, methodName, responseClass.getName());
//        } else {
//            Type type = method.getGenericReturnType();
//            if(Objects.nonNull(type) && type instanceof ParameterizedType) {
//                Type parameterType = ((ParameterizedType)type).getActualTypeArguments()[0];
//                if(Objects.nonNull(parameterType) && parameterType instanceof ParameterizedType) {
//                    Class<?> dataClass = (Class<?>)parameterType;
//                    if(Object.class.isAssignableFrom(dataClass) || Map.class.isAssignableFrom(dataClass) || JSONObject.class.isAssignableFrom(dataClass)) {
//                        log.error("【代码不规范提醒】{}#{} --> 接口响应Response泛型参数不允许使用！{}", controllerName, methodName, dataClass.getName());
//                    }
//                }
//            }
//        }
        //检查UrlAuth
        if(method.isAnnotationPresent(UrlAuth.class)){
            UrlAuth urlAuth = method.getDeclaredAnnotation(UrlAuth.class);
            String[] urlCodeCodes = urlAuth.value();
            if(ArrayUtils.isNotEmpty(urlCodeCodes)) {
                for(String urlCodeCode : urlCodeCodes) {
                    if(!urlCodeMap.containsKey(urlCodeCode)) {
                        urlCodeMap.put(urlCodeCode, new AtomicInteger(0));
                    }
                    urlCodeMap.get(urlCodeCode).incrementAndGet();
                }
            }
        }
        //禁止出参-VO中包含继承entity（便于开发，不做禁止）

        //入参DTO中包含继承Entity及各基类
        Class<?>[] parameterTypes = method.getParameterTypes();
        if(ArrayUtils.isEmpty(parameterTypes)) {
            return;
        }
        Set<Class<?>> entitySet = TableInfoHelper.getTableInfos().stream().map(tableInfo -> tableInfo.getEntityType()).collect(Collectors.toSet());
        for(Class<?> parameterType : parameterTypes) {
            String parameterName = parameterType.getSimpleName();
            if(entitySet.contains(parameterType)) {
                log.error("【代码不规范提醒】{}#{} --> {} PO不允许作为接口入参DTO", controllerName, methodName, parameterName);
                continue;
            }
            Iterator<Class<?>> iterator = entitySet.iterator();
            while(iterator.hasNext()) {
                Class<?> entityClass = iterator.next();
                String entityName =  entityClass.getSimpleName();
                if(entityClass.isAssignableFrom(parameterType)) {
                    log.error("【代码不规范提醒】{}#{} --> 接口入参DTO不允许继承PO {} extends {}", controllerName, methodName, parameterName, entityName);
                }
            }
        }
    }
}
